package c03数据类型和运算符;

import org.junit.Test;

import java.math.BigDecimal;
import java.math.RoundingMode;

/**
 * Java中提供了大数字(超过16位有效位)的操作类,即 java.math.BinInteger 类和 java.math.BigDecimal 类,
 * 用于高精度计算.其中 BigInteger 类是针对大整数的处理类,而 BigDecimal 类则是针对大小数的处理类.
 * BigDecimal 类的实现用到了 BigInteger类,不同的是 BigDecimal 加入了小数的概念.
 * float和Double只能用来做科学计算或者是工程计算, 因为由于在运算的时候，float类型和double很容易丢失精度，
 * 所以一般不用来做计算货币。在商业计算中,对数字精度要求较高,必须使用 BigInteger 类和 BigDecimal 类,
 * 它支持任何精度的定点数,可以用它来精确计算货币值。
 *
 * @author yuanwl
 * @date 2019-05-17 17:47
 */
public class BigDecimalTest {
	@Test
	public void 初始化() {
		/**
		 * 不允许使用 BigDecimal(double val) 构造器，因为使用该构造器时有一定的不可预知性，
		 * 当程序使用new BigDecimal(0.1)创建一个BigDecimal对象时，它的值并不是0.1，
		 * 实际上是一个近似0.1的数
		 */
		BigDecimal b1 = new BigDecimal(0.1);

		/**
		 * 建议优先使用基于String的构造器，使用BigDecimal(String val)构造器时可以预知的，
		 * 写入new BigDecimal("0.1")将创建一个恰好等于0.1的BigDecimal
		 */
		BigDecimal b2 = new BigDecimal("0.1");

		/**
		 * 如果必须使用double浮点数作为BigDecimal构造器的参数时，不要使用double作为参数，
		 * 而应该通过BigDecimal.valueOf(double value)静态方法来创建对象
		 */
		BigDecimal b3 = BigDecimal.valueOf(0.1);
	}


    @Test
    public void 比较() { //总结: equals 比较 BigDecimal 有问题, 要用 compareTo !
        BigDecimal b1 = new BigDecimal(0.0);
        BigDecimal b2 = new BigDecimal("0.0");
        BigDecimal b3 = BigDecimal.valueOf(0.0);

        System.err.println(b1);
        System.err.println(b2);
        System.err.println(b3);

        System.err.println("equals 比较 ---------------------");
        System.err.println(b1.equals(b2));
        System.err.println(b1.equals(b3));
        System.err.println(b2.equals(b3));

        System.err.println("compareTo 比较 ------------------");
        System.err.println(b1.compareTo(b2));
        System.err.println(b2.compareTo(b3));
        System.err.println(b1.compareTo(b3));

        System.err.println("0 和 null 比较 ---------------------");
        BigDecimal b0 = BigDecimal.ZERO;
        System.err.println(b0.equals(null)); //false
//		System.err.println(b0.compareTo(null)); //报错

        System.err.println("最大值 比较 ---------------------");
        BigDecimal bmax = BigDecimal.valueOf(Double.MAX_VALUE);
        BigDecimal bmax2 = BigDecimal.valueOf(Double.MAX_VALUE);
        System.err.println(bmax);
        System.err.println(b0.compareTo(bmax));
        System.err.println(bmax.compareTo(bmax2));
    }

	@Test
	public void 除法() {
		/**
		 * 除法运算的函数定义如下
		 * BigDecimal.divide(BigDecimal divisor, int scale, RoundingMode roundingMode) ;
		 * scale为小数位数, 如果不传, 即调用两个参数的除法时, 默认小数为 0；
		 * roundingMode为小数模式；
		 */
		BigDecimal a = BigDecimal.valueOf(0.125);
		BigDecimal b = BigDecimal.valueOf(1);

		/**
		 * 远离零方向舍入。向远离0的方向舍入，也就是说，向绝对值最大的方向舍入，只要舍弃位非0即进位。
		 */
		System.err.println(a.divide(b, 2, BigDecimal.ROUND_UP));
		/**
		 * 趋向零方向舍入。向0方向靠拢，也就是说，向绝对值最小的方向输入，注意：所有的位都舍弃，不存在进位情况。
		 */
		System.err.println(a.divide(b, 2, BigDecimal.ROUND_DOWN));
		/**
		 * 向正无穷方向舍入(CEILING 是天花板的意思)。向正最大方向靠拢，如果是正数，舍入行为类似于ROUND_UP；
		 * 如果为负数，则舍入行为类似于ROUND_DOWN。注意：Math.round方法使用的即为此模式。
		 */
		System.err.println(a.divide(b, 2, BigDecimal.ROUND_CEILING));
		/**
		 * 向负无穷方向舍入(FLOOR 是地板的意思)。向负无穷方向靠拢，如果是正数，则舍入行为类似于 ROUND_DOWN；
		 * 如果是负数，则舍入行为类似于 ROUND_UP。
		 */
		System.err.println(a.divide(b, 2, BigDecimal.ROUND_FLOOR));

		/**
		 * 若舍弃部分>=5，则作 ROUND_UP；否则，作 ROUND_DOWN。这就是我们最最经典的四舍五入模式。
		 */
		System.err.println(a.divide(b, 2, BigDecimal.ROUND_HALF_UP));
		/**
		 * 若舍弃部分>5，则作 ROUND_UP；否则，作 ROUND_DOWN
		 */
		System.err.println(a.divide(b, 2, BigDecimal.ROUND_HALF_DOWN));
		/**
		 * 四舍六入，五分两种情况: 如果舍弃部分左边为奇数，则入位，否则舍去。
		 * 此舍入模式也称为“银行家舍入法”，主要在美国使用, 或在互联网金融行业使用。
		 * 注意，在重复进行一系列计算时，此舍入模式可以将累加错误减到最小。
		 */
		System.err.println(a.divide(b, 2, BigDecimal.ROUND_HALF_EVEN));


		/**
		 * 断言请求的操作具有精确的结果，因此不需要舍入。
		 * 如果对获得精确结果的操作指定此舍入模式，则抛出ArithmeticException。
		 */
		System.err.println(a.divide(b, 2, BigDecimal.ROUND_UNNECESSARY));
	}

	@Test
	public void 精度(){
		System.err.println(new BigDecimal("0.0511").multiply(new BigDecimal("100")).setScale(2, RoundingMode.CEILING));
	}
}
